package com.tiny.conf.client.spring;


import com.tiny.conf.client.annotation.TinyConf;
import com.tiny.conf.client.common.TinyConfConstant;
import com.tiny.conf.client.listener.BeanRefreshTinyConfListener;
import com.tiny.conf.core.TinyConfClient;
import com.tiny.conf.core.factory.TinyConfBaseFactory;
import com.tiny.conf.core.listener.TinyConfListenerFactory;
import com.tiny.conf.core.util.FieldReflectionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.*;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.util.ReflectionUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.Objects;

public class TinyConfFactory extends InstantiationAwareBeanPostProcessorAdapter
        implements InitializingBean, DisposableBean, BeanNameAware, BeanFactoryAware {

    private static Logger logger = LoggerFactory.getLogger(TinyConfFactory.class);

    // ---------------------- env config ----------------------

    private String envprop;        // like "xxl-conf.properties" or "file:/data/webapps/xxl-conf.properties", include the following env config

    private String zkaddress;
    private String zkdigest;
    private String env;

    public void setEnvprop(String envprop) {
        this.envprop = envprop;
    }

    public void setZkaddress(String zkaddress) {
        this.zkaddress = zkaddress;
    }

    public void setZkdigest(String zkdigest) {
        this.zkdigest = zkdigest;
    }

    public void setEnv(String env) {
        this.env = env;
    }



    // ---------------------- init/destroy ----------------------

    @Override
    public void afterPropertiesSet() throws Exception {
        if (envprop != null && envprop.trim().length() > 0) {
            TinyConfBaseFactory.init(envprop);
            // 初始化bean
            TinyConfListenerFactory.addListener(null, new BeanRefreshTinyConfListener());
        } else {
            TinyConfBaseFactory.init(zkaddress, zkdigest, env);
            // 初始化bean
            TinyConfListenerFactory.addListener(null, new BeanRefreshTinyConfListener());

        }
    }

    @Override
    public void destroy() throws Exception {
        TinyConfBaseFactory.destroy();
    }

    // ---------------------- post process / xml、annotation ----------------------

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        // 1、Annotation('@TinyConf')：resolves conf + watch
        if (!beanName.equals(this.beanName)) {
            //
            ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {
                @Override
                public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                    if (field.isAnnotationPresent(TinyConf.class)) {
                        String propertiesName = field.getName();
                        TinyConf tinyConf = field.getAnnotation(TinyConf.class);
                        // 拿到对应的属性名
                        String confKey = tinyConf.value();

                        // 从客户端获取到属性值，或者获取默认值
                        String confValue = TinyConfClient.get(confKey, tinyConf.defaultValue());

                        // resolves placeholders  解析占位符
                        BeanRefreshTinyConfListener.BeanField beanField = new BeanRefreshTinyConfListener.BeanField(beanName, propertiesName);
                        // 将confvale 反射会到bean中
                        refreshBeanField(beanField, confValue, bean);


                        if (tinyConf.callback()) {
                            // 添加监听器
                            BeanRefreshTinyConfListener.addBeanField(confKey, beanField);
                        }
                    }
                }
            });
        }


        return super.postProcessAfterInstantiation(bean, beanName);
    }

    @Override
    public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
        // 2、XML('$XxlConf{...}')：resolves placeholders + watch
        if (!beanName.equals(this.beanName)) {

            // 该行代码的功能是从pvs对象中获取所有属性值，并将其存储在pvArray数组中。
            PropertyValue[] pvArray = pvs.getPropertyValues();
            for (PropertyValue pv : pvArray) {
                // 该Java语句检查变量pv的getValue()方法返回值是否为TypedStringValue类型。若是，则执行相应代码块。
                // 简言之，它用于判断值的类型并据此进行处理。
                if (pv.getValue() instanceof TypedStringValue) {
                    // 获取属性名并存储在变量 propertyName 中。具体来说，它调用了 pv 对象的 getName 方法来取得属性名。
                    // 这里的 pv 应该是一个属性相关的对象。
                    String propertyName = pv.getName();
                    // 该行代码功能如下：
                    //从参数pv中获取getValue()方法返回的对象。
                    //将该对象强制转换为TypedStringValue类型。
                    //从转换后的对象中再次调用getValue()方法，获取其值，并将此值赋给字符串变量typeStringVal。
                    String typeStringVal = ((TypedStringValue) pv.getValue()).getValue();
                    if (xmlKeyValid(typeStringVal)) {

                        // object + property
                        // 使用xmlKeyParse方法处理配置项键值对的字符串形式，得到配置项的键confKey；
                        String confKey = xmlKeyParse(typeStringVal);
                        // 通过XxlConfClient.get方法从配置中心获取该键对应的配置值confValue。
                        String confValue = TinyConfClient.get(confKey, "");

                        // resolves placeholders
                        // 解析占位符：创建BeanField对象，包含Bean名称和属性名，用于后续解析和更新属性值。
                        BeanRefreshTinyConfListener.BeanField beanField = new BeanRefreshTinyConfListener.BeanField(beanName, propertyName);
                        //refreshBeanField(beanField, confValue, bean);

                        //属性类型匹配：遍历PropertyDescriptor集合，查找与BeanField属性名匹配的PropertyDescriptor，以确定属性的类型propClass。
                        Class propClass = String.class;
                        for (PropertyDescriptor item : pds) {
                            if (beanField.getProperty().equals(item.getName())) {
                                propClass = item.getPropertyType();
                            }
                        }
                        // 值转换与更新：利用FieldReflectionUtil.parseValue方法，根据属性类型将配置值转换为相应的Java对象valueObj，并设置为属性的转换值。
                        Object valueObj = FieldReflectionUtil.parseValue(propClass, confValue);
                        // 该行代码将valueObj的值设置为pv对象的convertedValue属性。具体步骤如下：
                        //调用pv对象的setConvertedValue方法。
                        //该方法接收一个参数，即要设置的新值valueObj。
                        //执行该方法后，pv对象的convertedValue属性被更新为valueObj。
                        pv.setConvertedValue(valueObj);

                        // watch
                        BeanRefreshTinyConfListener.addBeanField(confKey, beanField);

                    }
                }
            }

        }

        return super.postProcessPropertyValues(pvs, pds, bean, beanName);
    }


    // ---------------------- refresh bean with xxl conf  ----------------------

    /**
     * refresh bean with xxl conf (fieldNames)
     */
    public static void refreshBeanField(final BeanRefreshTinyConfListener.BeanField beanField, final String value, Object bean) {
        if (bean == null) {
            bean = TinyConfFactory.beanFactory.getBean(beanField.getBeanName());        // 已优化：启动时禁止实用，getBean 会导致Bean提前初始化，风险较大；
        }
        if (bean == null) {
            return;
        }
        /**
         * 该代码片段的功能是创建一个BeanWrapper实例，用于包装指定的Bean对象。
         * BeanWrapper是Spring框架中的一个类，它提供了一种方便的方式来访问和修改一个对象的属性。
         * 通过使用BeanWrapper，可以在不直接依赖具体类的情况下，操作对象的字段值、调用方法等。
         * 这里的BeanWrapperImpl是BeanWrapper的一个具体实现类。
         */
        BeanWrapper beanWrapper = new BeanWrapperImpl(bean);

        //
        // property descriptor
        // 这段Java代码的功能是查找一个特定属性的描述符(PropertyDescriptor)。
        //初始化一个名为propertyDescriptor的变量为null，用于存储找到的属性描述符。
        //从beanWrapper对象中获取所有属性的描述符，并存储在propertyDescriptors数组中。
        //检查propertyDescriptors数组是否非空且含有元素。
        //遍历propertyDescriptors数组，对于每个属性描述符item：
        //比较指定属性名（beanField.getProperty()）是否与当前属性描述符的名称一致。
        //如果匹配，则将该属性描述符赋值给propertyDescriptor变量。
        //总之，这段代码旨在通过反射机制，根据属性名查找并获取相应的属性描述符。
        PropertyDescriptor propertyDescriptor = null;
        PropertyDescriptor[] propertyDescriptors = beanWrapper.getPropertyDescriptors();
        if (propertyDescriptors != null && propertyDescriptors.length > 0) {
            for (PropertyDescriptor item : propertyDescriptors) {
                if (beanField.getProperty().equals(item.getName())) {
                    propertyDescriptor = item;
                }
            }
        }

        // refresh field: set or field
        if (propertyDescriptor != null && propertyDescriptor.getWriteMethod() != null) {
            // 该行Java代码的功能是使用BeanWrapper类来设置指定bean的属性值。具体解释如下：
            //beanWrapper：这是一个BeanWrapper对象，用于包装一个bean，并提供对bean的属性进行读写的方法。
            //beanField.getProperty()：此方法调用获取bean的属性名称。
            //setValue方法：BeanWrapper的setValue方法用于设置bean的指定属性值。
            //mult data types：这里提到支持多种数据类型，意味着BeanWrapper可以处理不同数据类型的属性设置，而不仅仅是字符串或数字等单一类型。
            //综上所述，这行代码的功能是：使用BeanWrapper设置指定bean的某个属性值，且支持多种数据类型的属性。
            beanWrapper.setPropertyValue(beanField.getProperty(), value);    // support mult data types
            logger.info(">>>>>>>>>>> xxl-conf, refreshBeanField[set] success, {}#{}:{}",
                    beanField.getBeanName(), beanField.getProperty(), value);
        } else {

            final Object finalBean = bean;
            ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {
                @Override
                public void doWith(Field fieldItem) throws IllegalArgumentException, IllegalAccessException {
                    if (beanField.getProperty().equals(fieldItem.getName())) {
                        try {
                            /**
                             *
                             * 该函数用于将property对象转换为与field字段类型匹配的对象。具体功能如下：
                             * 调用converter的convertIfNecessary方法。
                             * 参数为property和field.getType()。
                             * 返回转换后的property对象。
                             * 简言之，此函数实现了属性值根据字段类型的自动类型转换。
                             */
                            Object valueObj = FieldReflectionUtil.parseValue(fieldItem.getType(), value);
                            // 该行代码将fieldItem字段的访问权限设置为true，使得可以访问私有或受保护的字段。
                            // 这通常用于反射操作中，以便能够在运行时访问类的私有成员。
                            fieldItem.setAccessible(true);

                            // 通过fieldItem设置finalBean对象的属性值为valueObj；支持多种数据类型。
                            // 为 finalBean 对象的 fieldItem 字段设置该新值。
                            fieldItem.set(finalBean, valueObj);        // support mult data types

                            logger.info(">>>>>>>>>>> xxl-conf, refreshBeanField[field] success, {}#{}:{}",
                                    beanField.getBeanName(), beanField.getProperty(), value);
                        } catch (IllegalAccessException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            });
        }
    }

    // ---------------------- util ----------------------
    private static SimpleTypeConverter converter = new SimpleTypeConverter();

    /**
     * 该方法将property对象转换为适合field字段类型的值。
     * 使用converter进行类型转换。
     * 参数：field表示目标字段，property是要转换的原始对象。
     * 返回：转换后的对象。
     *
     * @param field
     * @param property
     * @return
     */
    private static Object convertPropertyForField(final Field field, final Object property) {
        return converter.convertIfNecessary(property, field.getType());
    }

    /**
     * register beanDefinition If Not Exists
     *
     * @param registry
     * @param beanClass
     * @param beanName
     * @return
     */
    public static boolean registerBeanDefinitionIfNotExists(BeanDefinitionRegistry registry, Class<?> beanClass, String beanName) {

        // default bean name
        if (beanName == null) {
            beanName = beanClass.getName();
        }

        if (registry.containsBeanDefinition(beanName)) {    // avoid beanName repeat
            return false;
        }

        String[] beanNameArr = registry.getBeanDefinitionNames();
        for (String beanNameItem : beanNameArr) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(beanNameItem);
            if (Objects.equals(beanDefinition.getBeanClassName(), beanClass.getName())) {    // avoid className repeat
                return false;
            }
        }

        BeanDefinition annotationProcessor = BeanDefinitionBuilder.genericBeanDefinition(beanClass).getBeanDefinition();
        registry.registerBeanDefinition(beanName, annotationProcessor);
        return true;
    }


    /**
     * valid xml
     *
     * @param originKey
     * @return
     */
    private static boolean xmlKeyValid(String originKey) {
        boolean start = originKey.startsWith(TinyConfConstant.placeholderPrefix);
        boolean end = originKey.endsWith(TinyConfConstant.placeholderSuffix);
        if (start && end) {
            return true;
        }
        return false;
    }

    /**
     * parse xml
     *
     * @param originKey
     * @return
     */
    private static String xmlKeyParse(String originKey) {
        if (xmlKeyValid(originKey)) {
            // replace by xxl-conf
            String key = originKey.substring(TinyConfConstant.placeholderPrefix.length(), originKey.length() - TinyConfConstant.placeholderSuffix.length());
            return key;
        }
        return null;
    }


    // ---------------------- other ----------------------
    private static BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }


    private String beanName;

    @Override
    public void setBeanName(String s) {
        this.beanName = s;
    }


}
